package com.afkt.project.ui.widget.render.shapechange;


import com.afkt.project.interpolator.AccelerateInterpolator;
import com.afkt.project.interpolator.DecelerateInterpolator;
import com.afkt.project.interpolator.FastOutSlowInInterpolator;
import com.afkt.project.interpolator.Interpolator;
import com.afkt.project.util.LogUtils;
import com.dev.utils.app.SizeUtils;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;

public class DanceLoadingRenderer extends Component {

    private static final Interpolator MATERIAL_INTERPOLATOR   = new FastOutSlowInInterpolator();
    private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
    private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();

    private static final long ANIMATION_DURATION = 1888;

    private static final float DEFAULT_CENTER_RADIUS     = 12.5f;
    private static final float DEFAULT_STROKE_WIDTH      = 1.5f;
    private static final float DEFAULT_DANCE_BALL_RADIUS = 2.0f;

    private static final int NUM_POINTS           = 3;
    private static final int DEGREE_360           = 360;
    private static final int RING_START_ANGLE     = -90;
    private static final int DANCE_START_ANGLE    = 0;
    private static final int DANCE_INTERVAL_ANGLE = 60;

    private static final int DEFAULT_COLOR = Color.WHITE.getValue();

    //the center coordinate of the oval
    private static final float[] POINT_X   = new float[NUM_POINTS];
    private static final float[] POINT_Y   = new float[NUM_POINTS];
    //1: the coordinate x from small to large; -1: the coordinate x from large to small
    private static final int[]   DIRECTION = new int[]{1, 1, -1};

    private static final float BALL_FORWARD_START_ENTER_DURATION_OFFSET = 0f;
    private static final float BALL_FORWARD_END_ENTER_DURATION_OFFSET   = 0.125f;

    private static final float RING_FORWARD_START_ROTATE_DURATION_OFFSET = 0.125f;
    private static final float RING_FORWARD_END_ROTATE_DURATION_OFFSET   = 0.375f;

    private static final float CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET = 0.225f;
    private static final float CENTER_CIRCLE_FORWARD_END_SCALE_DURATION_OFFSET   = 0.475f;

    private static final float BALL_FORWARD_START_EXIT_DURATION_OFFSET = 0.375f;
    private static final float BALL_FORWARD_END_EXIT_DURATION_OFFSET   = 0.54f;

    private static final float RING_REVERSAL_START_ROTATE_DURATION_OFFSET = 0.5f;
    private static final float RING_REVERSAL_END_ROTATE_DURATION_OFFSET   = 0.75f;

    private static final float BALL_REVERSAL_START_ENTER_DURATION_OFFSET = 0.6f;
    private static final float BALL_REVERSAL_END_ENTER_DURATION_OFFSET   = 0.725f;

    private static final float CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET = 0.675f;
    private static final float CENTER_CIRCLE_REVERSAL_END_SCALE_DURATION_OFFSET   = 0.875f;

    private static final float BALL_REVERSAL_START_EXIT_DURATION_OFFSET = 0.875f;
    private static final float BALL_REVERSAL_END_EXIT_DURATION_OFFSET   = 1.0f;

    private final Paint mPaint         = new Paint();
    private final RectFloat mTempBounds    = new RectFloat();
    private final RectFloat mCurrentBounds = new RectFloat();

    private float mScale;
    private float mRotation;
    private float mStrokeInset;

    private float mCenterRadius;
    private float mStrokeWidth;
    private float mDanceBallRadius;
    private float mShapeChangeWidth;
    private float mShapeChangeHeight;

    private int mColor;
    private int mArcColor;

    public DanceLoadingRenderer(Context context) {
        super(context);
        init();
    }

    public DanceLoadingRenderer(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init();
    }

    public DanceLoadingRenderer(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init();
    }

    public DanceLoadingRenderer(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
        init();
    }
    protected final Rect mBounds = new Rect();


    private AnimatorValue animatorValue = new AnimatorValue();

    protected long mDuration;

    protected float mWidth;
    protected float mHeight;

    private void init(){

        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.getIntColor("#3F51B5")));
        setBackground(shapeElement);
        mStrokeWidth = SizeUtils.dipConvertPx(DEFAULT_STROKE_WIDTH) * 30;
        mCenterRadius = SizeUtils.dipConvertPx(DEFAULT_CENTER_RADIUS) * 3;
        mDanceBallRadius = SizeUtils.dipConvertPx(DEFAULT_DANCE_BALL_RADIUS) * 3;

        setColor(DEFAULT_COLOR);
        setInsets((int) mWidth, (int) mHeight);
        mDuration = ANIMATION_DURATION;

        animatorValue.setLoopedCount(AnimatorValue.INFINITE);
        animatorValue.setDuration(mDuration);
        animatorValue.setValueUpdateListener(mAnimatorUpdateListener);
        setupPaint();
        setLayoutRefreshedListener(new LayoutRefreshedListener() {
            @Override
            public void onRefreshed(Component component) {
                animatorValue.stop();
                animatorValue.start();
            }
        });
       // addDrawTask(drawTask);
    }

    public void stop() {
        animatorValue.stop();
    }





    private static String TAG = DanceLoadingRenderer.class.getSimpleName();



    private DrawTask drawTask = new DrawTask() {
        @Override
        public void onDraw(Component component, Canvas canvas) {
            LogUtils.error(TAG, "onDraw");

            int saveCount = canvas.save();

            mTempBounds.left = getLeft();
            mTempBounds.right = getRight();
            mTempBounds.bottom = getBottom();
            mTempBounds.top = getTop();
            mTempBounds.left += mStrokeInset;
            mTempBounds.top += mStrokeInset;
            mTempBounds.right -= mStrokeInset;
            mTempBounds.bottom -= mStrokeInset;
            copy(mCurrentBounds, mTempBounds);


            float outerCircleRadius = Math.min(mTempBounds.getHeight() - 600, mTempBounds.getWidth() - 600) / 2.0f;
            float interCircleRadius = outerCircleRadius / 2.0f;
            float centerRingWidth = interCircleRadius - mStrokeWidth / 2;

            mPaint.setStyle(Paint.Style.STROKE_STYLE);
            mPaint.setColor(new Color(mColor));
            mPaint.setStrokeWidth(mStrokeWidth);
            LogUtils.error(TAG, "X = " + mTempBounds.getCenter().getPointX()
                    + "Y = " + mTempBounds.getCenter().getPointY() + " outerCircleRadius = "+outerCircleRadius);
            canvas.drawCircle(mTempBounds.getCenter().getPointX(), mTempBounds.getCenter().getPointY(), outerCircleRadius, mPaint);
            mPaint.setStyle(Paint.Style.FILL_STYLE);
            canvas.drawCircle(mTempBounds.getCenter().getPointX(), mTempBounds.getCenter().getPointY(), interCircleRadius * mScale, mPaint);

            LogUtils.error(TAG, "a = " + (centerRingWidth/2.0f + mStrokeWidth / 2.0f) + " b = " + (centerRingWidth / 2.0f + mStrokeWidth / 2.0f));
            if (mRotation != 0) {
                mPaint.setColor(new Color(mArcColor));
                mPaint.setStyle(Paint.Style.STROKE_STYLE);
                //strokeWidth / 2.0f + mStrokeWidth / 2.0f is the center of the inter circle width
                inset(mTempBounds, centerRingWidth / 2.0f + mStrokeWidth / 2.0f, centerRingWidth / 2.0f + mStrokeWidth / 2.0f);
                mPaint.setStrokeWidth(centerRingWidth);
                mTempBounds.top += 710;
                mTempBounds.bottom -= 710;
                mTempBounds.left += 300;
                mTempBounds.right -= 300;
                canvas.drawArc(mTempBounds, new Arc(RING_START_ANGLE, mRotation, false), mPaint);
            }
            mPaint.setColor(new Color(mColor));
            mPaint.setStyle(Paint.Style.FILL_STYLE);
            for (int i = 0; i < NUM_POINTS; i++) {

                LogUtils.error(TAG, "X = " + POINT_X[i] + " Y = " + POINT_Y[i]);

                canvas.rotate(i * DANCE_INTERVAL_ANGLE, POINT_X[i], POINT_Y[i]);
                RectFloat rectF = new RectFloat(POINT_X[i] - mDanceBallRadius - mShapeChangeWidth / 2.0f - 50,
                        POINT_Y[i] - mDanceBallRadius - mShapeChangeHeight / 2.0f - 50 ,
                        POINT_X[i] + mDanceBallRadius + mShapeChangeWidth / 2.0f + 50 ,
                        POINT_Y[i] + mDanceBallRadius + mShapeChangeHeight / 2.0f + 50 ) ;

                LogUtils.error(TAG, "top = " + (POINT_Y[i] - mDanceBallRadius - mShapeChangeWidth / 2.0f) + " bottom = " +
                        (POINT_Y[i] + mDanceBallRadius + mShapeChangeHeight / 2.0f));
                canvas.drawOval(rectF, mPaint);

                canvas.rotate(-i * DANCE_INTERVAL_ANGLE, POINT_X[i], POINT_Y[i]);
            }
            canvas.restoreToCount(saveCount);
        }
    };

    private void inset(RectFloat src, float a, float b) {
        src.left += a;
        src.top += a;
        src.right -=b;
        src.bottom -=b;
    }

    private void copy(RectFloat src, RectFloat des) {
        src.left = des.left;
        src.right = des.right;
        src.bottom = des.bottom;
        src.top = des.top;
    }

    private AnimatorValue.ValueUpdateListener mAnimatorUpdateListener = new AnimatorValue.ValueUpdateListener() {

        @Override
        public void onUpdate(AnimatorValue animatorValue, float renderProgress) {
            float radius = Math.min(mCurrentBounds.getHeight(), mCurrentBounds.getWidth()) / 2.0f;
            //the origin coordinate is the centerLeft of the field mCurrentBounds
            float originCoordinateX = mCurrentBounds.left;
            float originCoordinateY = mCurrentBounds.top + radius + 450;

            if (renderProgress <= BALL_FORWARD_END_ENTER_DURATION_OFFSET && renderProgress > BALL_FORWARD_START_ENTER_DURATION_OFFSET) {
                final float ballForwardEnterProgress = (renderProgress - BALL_FORWARD_START_ENTER_DURATION_OFFSET) / (BALL_FORWARD_END_ENTER_DURATION_OFFSET - BALL_FORWARD_START_ENTER_DURATION_OFFSET);

                mShapeChangeHeight = (0.5f - ballForwardEnterProgress) * mDanceBallRadius / 2.0f;
                mShapeChangeWidth = -mShapeChangeHeight;
                //y = k(x - r)--> k = tan(angle)
                //(x - r)^2 + y^2 = r^2
                // compute crossover point --> (k(x -r)) ^ 2 + (x - )^2 = r^2
                // so x --> [r + r / sqrt(k ^ 2 + 1), r - r / sqrt(k ^ 2 + 1)]
                for (int i = 0; i < NUM_POINTS; i++) {
                    float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI));
                    // progress[-1, 1]
                    float progress = (ACCELERATE_INTERPOLATOR.getInterpolation(ballForwardEnterProgress) / 2.0f - 0.5f) * 2.0f * DIRECTION[i];
                    POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f)));
                    POINT_Y[i] = k * (POINT_X[i] - radius);

                    POINT_X[i] += originCoordinateX;
                    POINT_Y[i] += originCoordinateY;
                }
            }

            if (renderProgress <= RING_FORWARD_END_ROTATE_DURATION_OFFSET && renderProgress > RING_FORWARD_START_ROTATE_DURATION_OFFSET) {
                final float forwardRotateProgress = (renderProgress - RING_FORWARD_START_ROTATE_DURATION_OFFSET) / (RING_FORWARD_END_ROTATE_DURATION_OFFSET - RING_FORWARD_START_ROTATE_DURATION_OFFSET);
                mRotation = DEGREE_360 * MATERIAL_INTERPOLATOR.getInterpolation(forwardRotateProgress);
            }

            if (renderProgress <= CENTER_CIRCLE_FORWARD_END_SCALE_DURATION_OFFSET && renderProgress > CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET) {
                final float centerCircleScaleProgress = (renderProgress - CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET) / (CENTER_CIRCLE_FORWARD_END_SCALE_DURATION_OFFSET - CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET);

                if (centerCircleScaleProgress <= 0.5f) {
                    mScale = 1.0f + DECELERATE_INTERPOLATOR.getInterpolation(centerCircleScaleProgress * 2.0f) * 0.2f;
                } else {
                    mScale = 1.2f - ACCELERATE_INTERPOLATOR.getInterpolation((centerCircleScaleProgress - 0.5f) * 2.0f) * 0.2f;
                }
            }
            if (renderProgress <= BALL_FORWARD_END_EXIT_DURATION_OFFSET && renderProgress > BALL_FORWARD_START_EXIT_DURATION_OFFSET) {
                final float ballForwardExitProgress = (renderProgress - BALL_FORWARD_START_EXIT_DURATION_OFFSET) / (BALL_FORWARD_END_EXIT_DURATION_OFFSET - BALL_FORWARD_START_EXIT_DURATION_OFFSET);
                mShapeChangeHeight = (ballForwardExitProgress - 0.5f) * mDanceBallRadius / 2.0f;
                mShapeChangeWidth = -mShapeChangeHeight;
                for (int i = 0; i < NUM_POINTS; i++) {
                    float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI));
                    float progress = (DECELERATE_INTERPOLATOR.getInterpolation(ballForwardExitProgress) / 2.0f) * 2.0f * DIRECTION[i];
                    POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f)));
                    POINT_Y[i] = k * (POINT_X[i] - radius);

                    POINT_X[i] += originCoordinateX;
                    POINT_Y[i] += originCoordinateY;
                }
            }

            if (renderProgress <= RING_REVERSAL_END_ROTATE_DURATION_OFFSET && renderProgress > RING_REVERSAL_START_ROTATE_DURATION_OFFSET) {
                float scaledTime = (renderProgress - RING_REVERSAL_START_ROTATE_DURATION_OFFSET) / (RING_REVERSAL_END_ROTATE_DURATION_OFFSET - RING_REVERSAL_START_ROTATE_DURATION_OFFSET);
                mRotation = DEGREE_360 * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime) - 360;
            } else if (renderProgress > RING_REVERSAL_END_ROTATE_DURATION_OFFSET) {
                mRotation = 0.0f;
            }

            if (renderProgress <= BALL_REVERSAL_END_ENTER_DURATION_OFFSET && renderProgress > BALL_REVERSAL_START_ENTER_DURATION_OFFSET) {
                final float ballReversalEnterProgress = (renderProgress - BALL_REVERSAL_START_ENTER_DURATION_OFFSET) / (BALL_REVERSAL_END_ENTER_DURATION_OFFSET - BALL_REVERSAL_START_ENTER_DURATION_OFFSET);
                mShapeChangeHeight = (0.5f - ballReversalEnterProgress) * mDanceBallRadius / 2.0f;
                mShapeChangeWidth = -mShapeChangeHeight;

                for (int i = 0; i < NUM_POINTS; i++) {
                    float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI));
                    float progress = (0.5f - ACCELERATE_INTERPOLATOR.getInterpolation(ballReversalEnterProgress) / 2.0f) * 2.0f * DIRECTION[i];
                    POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f)));
                    POINT_Y[i] = k * (POINT_X[i] - radius);

                    POINT_X[i] += originCoordinateX;
                    POINT_Y[i] += originCoordinateY;
                }
            }

            if (renderProgress <= CENTER_CIRCLE_REVERSAL_END_SCALE_DURATION_OFFSET && renderProgress > CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET) {
                final float centerCircleScaleProgress = (renderProgress - CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET) / (CENTER_CIRCLE_REVERSAL_END_SCALE_DURATION_OFFSET - CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET);

                if (centerCircleScaleProgress <= 0.5f) {
                    mScale = 1.0f + DECELERATE_INTERPOLATOR.getInterpolation(centerCircleScaleProgress * 2.0f) * 0.2f;
                } else {
                    mScale = 1.2f - ACCELERATE_INTERPOLATOR.getInterpolation((centerCircleScaleProgress - 0.5f) * 2.0f) * 0.2f;
                }

            }

            if (renderProgress <= BALL_REVERSAL_END_EXIT_DURATION_OFFSET && renderProgress > BALL_REVERSAL_START_EXIT_DURATION_OFFSET) {
                final float ballReversalExitProgress = (renderProgress - BALL_REVERSAL_START_EXIT_DURATION_OFFSET) / (BALL_REVERSAL_END_EXIT_DURATION_OFFSET - BALL_REVERSAL_START_EXIT_DURATION_OFFSET);
                mShapeChangeHeight = (ballReversalExitProgress - 0.5f) * mDanceBallRadius / 2.0f;
                mShapeChangeWidth = -mShapeChangeHeight;

                for (int i = 0; i < NUM_POINTS; i++) {
                    float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI));
                    float progress = (0.0f - DECELERATE_INTERPOLATOR.getInterpolation(ballReversalExitProgress) / 2.0f) * 2.0f * DIRECTION[i];
                    POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f)));
                    POINT_Y[i] = k * (POINT_X[i] - radius);

                    POINT_X[i] += originCoordinateX;
                    POINT_Y[i] += originCoordinateY;
                }
            }

            addDrawTask(drawTask);
        }
    };


    private void init(Context context) {
        mStrokeWidth = SizeUtils.dipConvertPx(DEFAULT_STROKE_WIDTH);
        mCenterRadius = SizeUtils.dipConvertPx(DEFAULT_CENTER_RADIUS);
        mDanceBallRadius = SizeUtils.dipConvertPx(DEFAULT_DANCE_BALL_RADIUS);


        setColor(DEFAULT_COLOR);
        setInsets((int) mWidth, (int) mHeight);
        mDuration = ANIMATION_DURATION;
    }

    private void setColor(int color) {
        mColor = color;
        mArcColor = halfAlphaColor(mColor);
    }

        public void setRotation(float rotation) {
            mRotation = rotation;

        }

        private void setDanceBallRadius(float danceBallRadius) {
            this.mDanceBallRadius = danceBallRadius;

        }

        private float getDanceBallRadius() {
            return mDanceBallRadius;
        }

        public float getRotation() {
            return mRotation;
        }



    private void setInsets(
            int width,
            int height
    ) {
        final float minEdge = (float) Math.min(width, height);
        float       insets;
        if (mCenterRadius <= 0 || minEdge < 0) {
            insets = (float) Math.ceil(mStrokeWidth / 2.0f);
        } else {
            insets = minEdge / 2.0f - mCenterRadius;
        }
        mStrokeInset = insets;
    }

    private int halfAlphaColor(int colorValue) {
        int startA = (colorValue >> 24) & 0xff;
        int startR = (colorValue >> 16) & 0xff;
        int startG = (colorValue >> 8) & 0xff;
        int startB = colorValue & 0xff;

        return ((startA / 2) << 24)
                | (startR << 16)
                | (startG << 8)
                | startB;
    }


    private void setupPaint() {
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
    }


}
